#include "Template.h"
#include "Environment.h"
#include "ResourceManager.h"
#include "Phnqlet.h"
#include "TemplateCompiler.h"
#include "Tag.h"
#include "I18n.h"
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <iostream>
#include <sstream>
#include "util.h"
#include "kit.h"
#include "jsmin.h"

#define RUNTIME_FILE_DELIMITER "/*RUNNABLE_CODE_PLACEHOLDER*/"

namespace phnq
{
	namespace phnqlets
	{
		static phnq::kit::log::Logger *log = phnq::kit::log::Logger::get("phnq::phnqlets::Template");

		static map<int,Template*> templatesByIndex;
	
		static int nextIndex = 0;

		static string getRuntimeHeader()
		{
			string path = ResourceManager::getInstance()->findResource("js/runtime.js");
			string contents = util_get_file_contents(path.c_str());
			size_t idx = contents.find(RUNTIME_FILE_DELIMITER);
			return contents.substr(0, idx);
		}
	
		static string getRuntimeFooter()
		{
			string path = ResourceManager::getInstance()->findResource("js/runtime.js");
			string contents = util_get_file_contents(path.c_str());
			size_t idx = contents.find(RUNTIME_FILE_DELIMITER);
			return contents.substr(idx + strlen(RUNTIME_FILE_DELIMITER));
		}
	
		Template::Template(string path, string type, Phnqlet *phnqlet)
		{
			this->path = path;
			this->phnqlet = phnqlet;
			this->type = type;
			this->index = nextIndex++;
		
			templatesByIndex[this->index] = this;

			time(&initTime);
		}

		Template::~Template()
		{
		}
	
		Template* Template::getTemplate(int idx)
		{
			return templatesByIndex[idx];
		}
	
		string Template::getPath()
		{
			return path;		
		}
	
		int Template::getIndex()
		{
			return index;
		}
	
		string Template::getType()
		{
			return type;
		}
	
		void Template::insertObjectCodeAndDependencies(stringbuf &sb, set<Phnqlet*> &phnqlets, set<Tag*> &tags, set<int> *exceptions)
		{
			if(exceptions != NULL && (exceptions->find(this->getIndex()) != exceptions->end()))
				return;
		
			if(phnqlets.find(this->phnqlet) != phnqlets.end())
				return;
		
			phnqlets.insert(this->phnqlet);
			
			bool hasScriptFile;
			this->phnqlet->getScript(hasScriptFile);
			
			Phnqlet *p = this->phnqlet;
			string types = p->getType();
			p = p->getSuper();
			while(p)
			{
				types += " " + p->getType();
				p = p->getSuper();
			}
			
			string topClassName = (hasScriptFile ? "phnqlet_ni " + types : types) + " _{params['class']||''}";
		
			compile_result result = TemplateCompiler::compile(this->path.c_str(), topClassName.c_str(), this->phnqlet);
		
			for(set<Tag*>::iterator it=result.deps.tags.begin(); it!=result.deps.tags.end(); it++)
			{
				Tag *t = *it;
				if(tags.find(t) == tags.end())
				{
					tags.insert(t);
					string tagCode = t->getTagCode();
					sb.sputn(tagCode.c_str(), tagCode.length());
				}
			}
		
			for(set<Phnqlet*>::iterator it=result.deps.phnqlets.begin(); it!=result.deps.phnqlets.end(); it++)
			{
				Phnqlet *p = *it;
				Template *t = p->getTemplate("main");
				if(t)
					t->insertObjectCodeAndDependencies(sb, phnqlets, tags, exceptions);
				else
					phnqlets.insert(p); // A phnqlet may be only JS -- so add it anyway.
			}
		
			char c[256];

			sprintf(c, "ptypes[%i] = \"%s\";", this->phnqlet->getIndex(), this->phnqlet->getType().c_str());
			sb.sputn(c, strlen(c));
			
			sprintf(c, "i18ns[%i] = function(){", this->phnqlet->getIndex());
			sb.sputn(c, strlen(c));
			
			I18nStringsBundle *strings = this->phnqlet->getStrings();
			set<string> *allKeys = strings->getAllKeys();
			for(set<string>::iterator it=allKeys->begin(); it!=allKeys->end(); it++)
			{
				string k = *it;
				string *v = strings->getString(k);
				if(v)
				{
					sb.sputn("this['", 6);
					sb.sputn(k.c_str(), k.length());
					sb.sputn("']=", 3);
					
					UTHString *vStr = new UTHString(v->c_str());
					string vEsc = vStr->toString();
					vStr->release();
				
					sb.sputn(vEsc.c_str(), vEsc.length());
					sb.sputn(";", 1);
				}
			}
			
			sb.sputc('}');
			sb.sputc(';');
			
			// if this phnqlet extends another, then...
			// i18ns[j].prototype = new i18ns[k]();
			
			
			
			sprintf(c, "templateIndexes[\"%s\"] = %i;", getType().c_str(), getIndex());
			sb.sputn(c, strlen(c));
			sprintf(c, "templates[%i] = function(params, attrs){", getIndex());
			sb.sputn(c, strlen(c));
			sb.sputn(result.objectCode.c_str(), result.objectCode.length());
			sb.sputn("};\n\n", 4);
		}
	
		uint32_t Template::calculateDepHash(set<string> depPaths)
		{
			struct stat attrib;
			int dSize = sizeof(double);
			char dc[dSize];
			Hash *hash = new Hash((char*)"init");
		
			for(set<string>::iterator it=depPaths.begin(); it!=depPaths.end(); it++)
			{
				string path = *it;
				hash->update((char*)path.c_str(), path.length());
			
				stat(path.c_str(), &attrib);
				double d = difftime(attrib.st_mtime, initTime);
				memcpy(dc, &d, dSize);
				hash->update(dc, dSize);
			}
		
			return hash->getHash();
		}
	
		void Template::addClientSideFramework(stringbuf &sb, set<string> &depPaths)
		{
			vector<string> scripts;
			scripts.push_back("js/client.js");
			scripts.push_back("js/util.js");
			scripts.push_back("js/runtime.js");
			scripts.push_back("js/event.js");
			scripts.push_back("js/io.js");
			scripts.push_back("js/dom.js");
			scripts.push_back("js/phnqlets.js");
		
			for(vector<string>::iterator it=scripts.begin(); it!=scripts.end(); it++)
			{
				string script = *it;
			
				string scriptPath = ResourceManager::getInstance()->findResource(script);
				depPaths.insert(scriptPath);
				string scriptStr = util_get_file_contents(scriptPath.c_str());
				sb.sputn(scriptStr.c_str(), scriptStr.length());
			}
		}
	
		script_pool_item Template::getScriptFromPool()
		{
			boost::mutex::scoped_lock scoped_lock(scriptPoolMutex);
			
			string currentLocale = I18n::getCurrentLocale();
			
			stack<script_pool_item> *scriptPool = scriptPools[currentLocale];
			if(!scriptPool)
				scriptPool = scriptPools[currentLocale] = new stack<script_pool_item>();
		
			if(scriptPool->size() == 0)
			{
				stringbuf sb, aggScriptBuf, aggStyleBuf;
				set<Tag*> tags;
				set<Phnqlet*> phnqlets;

				script_pool_item pool_item;
				
				log->info("compiling template script: %s", this->path.c_str());

				string runtimePath = ResourceManager::getInstance()->findResource("js/runtime.js");
			
				pool_item.depPaths.insert(runtimePath);
			
				const char *fnBegin = "(function(){";
				const char *fnEnd = "})();";
				const char *evalCodeStmnt = "return phnq.runtime.eval();";

				string header = getRuntimeHeader();
				string footer = getRuntimeFooter();
			
				sb.sputn(fnBegin, strlen(fnBegin));
				sb.sputn(header.c_str(), header.length());

				insertObjectCodeAndDependencies(sb, phnqlets, tags);

				addClientSideFramework(aggScriptBuf, pool_item.depPaths);

				// For each phnqlet that was found to be a dependency:
				// - add its main template to the depPaths
				// - aggregate the script
				// - aggregate the style
				for(set<Phnqlet*>::iterator it=phnqlets.begin(); it!=phnqlets.end(); it++)
				{
					Phnqlet *p = *it;
				
					// Add the phnqlet's main template file to the depPaths set for stale triggering.
					Template *t = p->getTemplate("main");
					if(t)
						pool_item.depPaths.insert(t->path);
					
					string script = p->getScript();
					aggScriptBuf.sputn(script.c_str(), script.length());
					pool_item.depPaths.insert(p->getScriptFile());
			
					string style = p->getStyle();
					aggStyleBuf.sputn(style.c_str(), style.length());
					pool_item.depPaths.insert(p->getStyleFile());
				}

				// Add each tag file to the depPaths set for stale triggering.
				for(set<Tag*>::iterator it=tags.begin(); it!=tags.end(); it++)
				{
					Tag *t = *it;
					pool_item.depPaths.insert(t->getTagFilePath());
				}

				char topTmlptCall[32];
				sprintf(topTmlptCall, "templates[%i]({});", getIndex());
				sb.sputn(topTmlptCall, strlen(topTmlptCall));

				sb.sputn(footer.c_str(), footer.length());
				sb.sputn(evalCodeStmnt, strlen(evalCodeStmnt));
				sb.sputn(fnEnd, strlen(fnEnd));
			
				pool_item.script = UTHScript::New(sb.str().c_str());

				UTHObject *globals = hooks.getGlobalObject();
				UTHObject *phnqObj = (UTHObject*)globals->get("phnq");
				
				string aggScript = "(function(){"+aggScriptBuf.str()+"})();";
				phnqObj->set("script", aggScript);
				
				phnqObj->set("style", aggStyleBuf.str());
				
				pool_item.script->setGlobals(globals);
				pool_item.script->compile();
			
				pool_item.depHash = depHash = calculateDepHash(pool_item.depPaths);
				time(&depHashCalcTime);
			
				scriptPool->push(pool_item);
			}
		
			script_pool_item top_pool_item = scriptPool->top();
			scriptPool->pop();
		
			// Update depHash if needed...
			time_t t;
			time(&t);
			double d = difftime(t, depHashCalcTime);
			if(d > Environment::getInstance()->getStaleFileCheckInterval())
			{
				depHash = calculateDepHash(top_pool_item.depPaths);
				depHashCalcTime = t;
			
				if(depHash != top_pool_item.depHash)
				{
					log->info("stale dependencies found - flushing script pool: %s", this->path.c_str());
				
					while(scriptPool->size())
					{
						script_pool_item item = scriptPool->top();
						scriptPool->pop();
						delete item.script;
						item.script = NULL;
					}
				
					// Need to unlock to avoid deadlock.
					scoped_lock.unlock();
				
					// do recursive call -- should be fine.
					return getScriptFromPool();
				}
			}

			return top_pool_item;
		}
	
		void Template::returnScriptToPool(script_pool_item pool_item)
		{
			// after 1000 executions, don't put it back in the pool.
			if(pool_item.script->getExecCount() > 1000)
			{
				delete pool_item.script;
				pool_item.script = NULL;
				return;
			}
			
			boost::mutex::scoped_lock scoped_lock(scriptPoolMutex);
		
			if(pool_item.depHash == depHash)
			{
				stack<script_pool_item> *scriptPool = scriptPools[I18n::getCurrentLocale()];
				scriptPool->push(pool_item);
			}
		}
	
		string Template::process()
		{
			script_pool_item pool_item = getScriptFromPool();
		
			UTHValue *result = pool_item.script->execute();

			returnScriptToPool(pool_item);
			
			if(result->isString())
			{
				string resp = string(((UTHString*)result)->getChars());
				result->release();
				return "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" + resp;
			}
			else
			{
				cout << "result was not string: " << result->toString() << endl;
				
				result->release();
				return "nope template";
			}
		}
	
		string Template::processScript()
		{
			string s;
			script_pool_item pool_item = getScriptFromPool();
			UTHScript *script = pool_item.script;
			UTHObject *globals = script->getGlobals();
			if(globals)
			{
				UTHObject *phnqObj = (UTHObject*)globals->get("phnq");
				if(phnqObj)
				{
					UTHString *aggScript = (UTHString*)phnqObj->get("script");
					if(aggScript)
						s = string(jsmin(aggScript->getChars()));
				}
			}
			returnScriptToPool(pool_item);
			return s;
		}
	
		string Template::processStyle()
		{
			string s;
			script_pool_item pool_item = getScriptFromPool();
			UTHScript *script = pool_item.script;
			UTHObject *globals = script->getGlobals();
			if(globals)
			{
				UTHObject *phnqObj = (UTHObject*)globals->get("phnq");
				if(phnqObj)
				{
					UTHString *aggStyle = (UTHString*)phnqObj->get("style");
					if(aggStyle)
						s = string(aggStyle->getChars());
				}
			}
			returnScriptToPool(pool_item);
			return s;
		}
	}
}
